using System;
using System.Xml;

using DesignByContract;

using Outcoding.GraphGenerator.Entities;
using Outcoding.GraphGenerator.Util.Logging;

#region Comments
    //TODO: Choose a more elegant way to select the nodes in the composer methods
#endregion

namespace Outcoding.GraphGenerator.Composition {
    public class Composer {
        private readonly XmlDocument mDocument;
        private readonly BuilderDirector mDirector;

        public Presentation Product { get; private set; }

        public Composer(string xmlPath) {
            if (String.IsNullOrEmpty(xmlPath))
                throw new ArgumentNullException("XmlPath cannot be null or empty.");

            mDocument = new XmlDocument();
            try {
                mDocument.Load(xmlPath);
            }catch(XmlException xmlExc){
                LogManager.Instance.LogError("Could not load XmlDocument.", xmlExc);
                mDocument = null;
            } catch (ArgumentException argExc) {
                LogManager.Instance.LogError("Could not load XmlDocument.", argExc);
                mDocument = null;
            } catch (System.IO.IOException ioExc) {
                LogManager.Instance.LogError("Could not load XmlDocument.", ioExc);
                mDocument = null;
            } catch (Exception exc) {
                LogManager.Instance.LogError("Could not load XmlDocument.", exc);
                mDocument = null;
            }

            mDirector = new BuilderDirector{Document = mDocument};
        }

        public void Compose() {
            ComposePresentation();
        }

        private void ComposePresentation() {
            BuilderBase builder = new PresentationBuilder();
            mDirector.Build(builder, mDocument, null);
            Product = (Presentation)builder.Product;

            //compose childs
            ComposeIndustry(Product);
            ComposeSlideCollection(Product);
        }

        private void ComposeIndustry(Presentation presentation) {
            Check.Require(presentation != null, "Presentation cannot be empty.");

            BuilderBase builder = new IndustryBuilder();
            mDirector.Build(builder, mDocument, presentation);
        }

        private void ComposeSlideCollection(Presentation presentation) {
            Check.Require(presentation != null, "Presentation cannot be empty.");

            BuilderBase builder = new SlideCollectionBuilder();
            mDirector.Build(builder, mDocument, presentation);

            //compose childs
            ComposeSlides((SlideCollection)builder.Product);
        }

        private void ComposeSlides(SlideCollection slideCollection) {
            Check.Require(slideCollection != null, "SlideCollection cannot be empty.");

            BuilderBase builder = new SlideBuilder();
            mDirector.Build(builder, mDocument, slideCollection);

            //compose childs
            XmlNodeList slideList = mDocument.SelectNodes("/presentation/slidecollection/slide");
            for (int i = 0; i < slideList.Count; i++){
                XmlNode slideNode = slideList[i];
                Slide slide = slideCollection[i];
                ComposeShapes(slideNode, slide);
			}
        }

        private void ComposeShapes(XmlNode node, Slide slide) {
            Check.Require(slide != null, "Slide cannot be empty.");

            BuilderBase builder = new ShapeBuilder();
            mDirector.Build(builder, node, slide);

            //compose childs
            XmlNodeList shapeList = node.SelectNodes("./shape");
            for (int i = 0; i < shapeList.Count; i++) {
                XmlNode shapeNode = shapeList[i];
                Shape shape = slide.Shapes[i];
                ComposeContent(shapeNode, shape);
            }
        }

        private void ComposeContent(XmlNode node, Shape shape) {
            Check.Require(shape != null, "Shape cannot be empty.");

            if (node.FirstChild.Name.CompareTo("label") == 0) {     //compose label
                BuilderBase builder = new LabelBuilder();
                mDirector.Build(builder, node, shape);
            }else if (node.FirstChild.Name.CompareTo(XmlTagNames.image.ToString("g")) == 0) {
                BuilderBase builder = new ImageBuilder();
                mDirector.Build(builder, node, shape);
            }
            else {                                                 //compose graph
                BuilderBase builder = new GraphBuilder();
                mDirector.Build(builder, node, shape);

                //compose childs
                XmlNode graphNode = node.SelectSingleNode("./graph");
                ComposeLabel(graphNode, (Graph)builder.Product);
                ComposeLegend(graphNode, (Graph)builder.Product);
                ComposeDatatable(graphNode, (Graph)builder.Product);
                ComposeAxes(graphNode, (Graph)builder.Product);
                ComposeSeriesCollection(graphNode, (Graph)builder.Product);
            }
        }

        private void ComposeLabel(XmlNode node, Graph graph) {
            Check.Require(graph != null, "Graph cannot be empty.");

            BuilderBase builder = new LabelBuilder();
            mDirector.Build(builder, node, graph);
        }

        private void ComposeLegend(XmlNode node, Graph graph) {
            Check.Require(graph != null, "Graph cannot be empty.");

            BuilderBase builder = new LegendBuilder();
            mDirector.Build(builder, node, graph);
        }

        private void ComposeDatatable(XmlNode node, Graph graph) {
            Check.Require(graph != null, "Graph cannot be empty.");

            BuilderBase builder = new DatatableBuilder();
            mDirector.Build(builder, node, graph);
        }

        private void ComposeAxes(XmlNode node, Graph graph) {
            Check.Require(graph != null, "Graph cannot be empty.");

            BuilderBase builder = new AxesBuilder();
            mDirector.Build(builder, node, graph);

            //compose childs                
            XmlNode axesNode = node.SelectSingleNode("./axes");
            ComposeValueAxis(axesNode, (Axes)builder.Product);
            ComposeCategoryAxis(axesNode, (Axes)builder.Product);
            ComposeSeriesAxis(axesNode, (Axes)builder.Product);
        }

        private void ComposeValueAxis(XmlNode node, Axes axes) {
            Check.Require(axes != null, "Axes cannot be empty.");

            BuilderBase builder = new ValueAxisBuilder();
            mDirector.Build(builder, node, axes);

            //compose childs
            XmlNode axesNode = node.SelectSingleNode("./valueaxis");
            ComposeLabel(axesNode, (IAxis)builder.Product);
            ComposeMajorGridline(axesNode, (ValueAxis)builder.Product);
            ComposeMinValue(axesNode, (ValueAxis)builder.Product);
            ComposeMaxValue(axesNode, (ValueAxis)builder.Product);
            ComposeScaleValue(axesNode, (ValueAxis)builder.Product);
        }

        private void ComposeMajorGridline(XmlNode node, ValueAxis valueAxis) {
            Check.Require(valueAxis != null, "ValueAxis cannot be empty.");

            BuilderBase builder = new MajorGridlineBuilder();
            mDirector.Build(builder, node, valueAxis);
        }

        private void ComposeLabel(XmlNode node, IAxis axis) {
            Check.Require(axis != null, "IAxis object cannot be empty.");

            BuilderBase builder = new LabelBuilder();
            mDirector.Build(builder, node, axis);
        }

        private void ComposeMinValue(XmlNode node, ValueAxis valueAxis) {
            Check.Require(valueAxis != null, "ValueAxis cannot be empty.");

            BuilderBase builder = new MinValueBuilder();
            mDirector.Build(builder, node, valueAxis);
        }

        private void ComposeMaxValue(XmlNode node, ValueAxis valueAxis) {
            Check.Require(valueAxis != null, "ValueAxis cannot be empty.");

            BuilderBase builder = new MaxValueBuilder();
            mDirector.Build(builder, node, valueAxis);
        }

        private void ComposeScaleValue(XmlNode node, ValueAxis valueAxis) {
            Check.Require(valueAxis != null, "ValueAxis cannot be empty.");

            BuilderBase builder = new ScaleValueBuilder();
            mDirector.Build(builder, node, valueAxis);
        }

        private void ComposeCategoryAxis(XmlNode node, Axes axes) {
            Check.Require(axes != null, "Axes cannot be empty.");

            BuilderBase builder = new CategoryAxisBuilder();
            mDirector.Build(builder, node, axes);

            //compose childs
            XmlNode axesNode = node.SelectSingleNode("./categoryaxis");
            ComposeLabel(axesNode, (IAxis)builder.Product);
            ComposeLabelCollection(axesNode, (CategoryAxis)builder.Product);
        }

        private void ComposeLabelLine(CategoryAxis categoryAxis) {
            if (categoryAxis.Labels != null) {
                LineColorDispatcher colorDispatcher = new LineColorDispatcher();
                MarkerStyleDispatcher markerDispatcher = new MarkerStyleDispatcher();
                foreach (Label label in categoryAxis.Labels) {
                    label.Line = new Line();
                    label.Line.MarkerSize = categoryAxis.DefaultMarkerSize;
                    label.Line.IsSmooth = categoryAxis.IsSmoothed;
                    label.Line.MarkerShape = (MarkerStyle)markerDispatcher.Pop();
                }
            }
        }

        private void ComposeLabelCollection(XmlNode node, CategoryAxis categoryAxis) {
            Check.Require(categoryAxis != null, "CategoryAxis cannot be empty.");

            BuilderBase builder = new LabelCollectionBuilder();
            mDirector.Build(builder, node, categoryAxis);

            //compose childs
            XmlNode labelsNode = node.SelectSingleNode("./labelcollection");
            ComposeLabelsForCollection(labelsNode, (LabelCollection)builder.Product);
        }

        private void ComposeLabelsForCollection(XmlNode node, LabelCollection labelCollection) {
            Check.Require(labelCollection != null, "CategoryAxis cannot be empty.");

            BuilderBase builder = new CategoryLabelBuilder();
            XmlNodeList labelList = node.SelectNodes("./categorylabel");
            foreach (XmlNode labelNode in labelList) {
                (builder as CategoryLabelBuilder).Node = labelNode;
                mDirector.Build(builder, labelCollection);
            }
        }

        private void ComposeSeriesAxis(XmlNode node, Axes axes) {
            Check.Require(axes != null, "Axes cannot be empty.");

            BuilderBase builder = new SeriesAxisBuilder();
            mDirector.Build(builder, node, axes);

            //compose childs
            XmlNode axesNode = node.SelectSingleNode("./seriesaxis");
            ComposeLabel(axesNode, (IAxis)builder.Product);
        }

        private void ComposeSeriesCollection(XmlNode node, Graph graph) {
            Check.Require(graph != null, "Graph cannot be empty.");

            BuilderBase builder = new SeriesCollectionBuilder();
            mDirector.Build(builder, node, graph);

            //compose childs
            ComposeSeries(node.SelectSingleNode("./seriescollection"), (SeriesCollection)builder.Product);
        }

        private void ComposeSeries(XmlNode node, SeriesCollection seriesCollection) {
            Check.Require(seriesCollection != null, "Graph cannot be empty.");

            BuilderBase builder = new SeriesBuilder();
            XmlNodeList seriesNodeList = node.SelectNodes("./series");
            foreach (XmlNode seriesNode in seriesNodeList) {
                mDirector.Build(builder, seriesNode, seriesCollection);

                //compose childs
                ComposeSeriesLabel(seriesNode, (Series)builder.Product);
                ComposePointCollection(seriesNode, (Series)builder.Product);
            }
        }

        private void ComposeSeriesLabel(XmlNode node, Series series) {
            Check.Require(series != null, "Series object cannot be empty.");

            BuilderBase builder = new LabelBuilder();
            mDirector.Build(builder, node, series);
        }

        private void ComposePointCollection(XmlNode node, Series series) {
            Check.Require(series != null, "Series object cannot be empty.");

            BuilderBase builder = new PointCollectionBuilder();
            mDirector.Build(builder, node, series);

            //compose childs
            XmlNode pointCollectionNode = node.SelectSingleNode("./pointcollection");
            ComposePoint(pointCollectionNode, (PointCollection)builder.Product);
        }

        private void ComposePoint(XmlNode node, PointCollection pointCollection) {
            Check.Require(pointCollection != null, "PointCollection object cannot be empty.");

            BuilderBase builder = new PointBuilder();
            
            //compose childs
            XmlNodeList pointList = node.SelectNodes("./point");
            foreach (XmlNode pointNode in pointList) {
                mDirector.Build(builder, pointNode, pointCollection);
                Point point = (Point) builder.Product;
                if(point.UseText) {
                    ComposePointLabel(pointNode, point);
                }
            }
        }

        private void ComposePointLabel(XmlNode node, Point point)
        {
            Check.Require(point != null, "Point cannot be empty.");

            BuilderBase builder = new LabelBuilder();
            mDirector.Build(builder, node, point);
        }
    }
}
